﻿Imports System
Imports System.CodeDom.Compiler
Imports System.Reflection
Imports Microsoft.VisualBasic
Imports System.Text

Namespace Util

    ''' <summary>
    ''' Interpreta uma string como sentença através da interpretação do tipo ItemEvaluator
    ''' </summary>
    ''' <remarks></remarks>
    ''' <example>
    ''' Dim eval as New Evaluator()
    ''' Dim numero as Decimal = eval.EvaluateDecimal("5 + 3 - 4 * (2 + 1)")
    ''' 
    ''' Dim eval as New Evaluator()
    ''' Dim numero as Decimal = eval.EvaluateBool(" ""A""=""A"" ")
    ''' </example>
    Public Class Evaluator
        Private _objeto As Object

        Public Sub New()

        End Sub

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_tipoRetorno"></param>
        ''' <param name="_expressaoEval"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Evaluate(ByVal _tipoRetorno As Type, ByVal _expressaoEval As String) As Object
            Dim _expressao As String = ParseAndOr(_expressaoEval)

            Try
                Dim byteCode As Assembly = ConstruirAssembly(_tipoRetorno, _expressao)
                Me._objeto = byteCode.CreateInstance("Fgv.Util._EvaluatorEmMemoria")

                Dim metodoInfo As MethodInfo = _objeto.GetType().GetMethod("_evaluate")
                Return metodoInfo.Invoke(_objeto, Nothing)
            Catch ex As Exception
                Me._objeto = Nothing
                Throw New Util.Excecao.EvaluatorException("Expressão incorreta. '" & _expressao & "' não retorna valor do tipo " & _tipoRetorno.Name & ". Exceção original: " & ex.Message)
                Return Nothing
            End Try
        End Function

        ''' <summary>
        ''' Realiza o parse do AND e OR que estão traduzidos pro português (E, OU)
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ParseAndOr(ByVal _expressao As String) As String
            Dim retorno As String = _expressao
            retorno = retorno.Replace(" E ", " AND ")
            retorno = retorno.Replace(" e ", " AND ")
            retorno = retorno.Replace(" OU ", " OR ")
            retorno = retorno.Replace(" ou ", " OR ")
            Return retorno
        End Function

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function EvaluateDecimal(ByVal _expressao As String) As Decimal
            Return Evaluate(GetType(Decimal), _expressao)
        End Function

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function EvaluateLong(ByVal _expressao As String) As Long
            Return Evaluate(GetType(Long), _expressao)
        End Function

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function EvaluateDouble(ByVal _expressao As String) As Double
            Return Evaluate(GetType(Double), _expressao)
        End Function

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function EvaluateSingle(ByVal _expressao As String) As Single
            Return Evaluate(GetType(Single), _expressao)
        End Function

        ''' <summary>
        ''' Caso o Evaluator não consiga resolver a expressão passada, será disparada uma exceção
        ''' do tipo Fgv.Util.Excecao.EvaluatorException (especialização de Exception)
        ''' 
        ''' Exception:
        ''' Fgv.Util.Excecao.EvaluatorException
        ''' </summary>
        ''' <param name="_expressao"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function EvaluateBool(ByVal _expressao As String) As Boolean
            Return Evaluate(GetType(Boolean), _expressao)
        End Function

        Private Function ConstruirAssembly(ByVal tipoRetorno As Type, ByVal expressao As String) As Assembly
            Dim compilador As ICodeCompiler = (New VBCodeProvider().CreateCompiler())
            Dim parametros As New CompilerParameters
            parametros.ReferencedAssemblies.Add("system.dll")
            parametros.GenerateExecutable = False
            parametros.GenerateInMemory = True

            Dim codigoFonte As New StringBuilder()
            codigoFonte.Append("Imports System " & vbCrLf)
            codigoFonte.Append("Namespace Fgv.Util " & vbCrLf)
            codigoFonte.Append(" Public Class _EvaluatorEmMemoria " & vbCrLf)
            codigoFonte.AppendFormat(" Public Function {1}() as {0} " & vbCrLf, tipoRetorno.Name, "_evaluate")
            codigoFonte.Append(" return " & expressao & vbCrLf)
            codigoFonte.Append(" End Function " & vbCrLf)
            codigoFonte.Append(" End Class " & vbCrLf)
            codigoFonte.Append(" End Namespace " & vbCrLf)

            Trace.WriteLine("----------------------------------")
            Trace.WriteLine(codigoFonte.ToString())
            Trace.WriteLine("----------------------------------")

            Return compilador.CompileAssemblyFromSource(parametros, codigoFonte.ToString()).CompiledAssembly

        End Function

    End Class

End Namespace

